﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Caching;
using Medianamik.Core;
using Medianamik.Core.Caching;
using Medianamik.UI.Web.Shop.Data;
using Medianamik.UI.Web.Shop.Data.Dto;
using Medianamik.UI.Web.Shop.Data.Repositories;
using Medianamik.UI.Web.Shop.Data.ViewModels;
using Medianamik.Core.Sugar;
using Model = Medianamik.UI.Web.Shop.Data.Model;

namespace Medianamik.UI.Web.Shop.Services
{
    public interface IProductService<TBrand, TCategory, TModel,TProduct>
        where TBrand : Data.Interfaces.IBrand
        where TModel : Data.Interfaces.IModel
        where TCategory : Data.Interfaces.ICategory
        where TProduct : Data.Interfaces.IProduct
    {
        IList<ProductBrandViewModel<TBrand, TModel, TCategory>> GetProductsGroupByBrand(Guid nodeId);
        IList<ProductDto<TBrand, TCategory, TModel>> GetAllProduct(CultureInfo culture);
        IList<ProductDto<TBrand, TCategory, TModel>> GetAllProduct(CultureInfo culture,
            ContentState contentState, ActiveStatus activeStatus);
        ProductDto<TBrand, TCategory, TModel> GetProduct(Guid nodeId);
        ProductDto<TBrand, TCategory, TModel> GetProduct(Guid productId, CultureInfo culture);
        IList<ProductDto<TBrand, TCategory, TModel>> SearchProduct(string keywords, CultureInfo culture);
        IList<ProductDto<TBrand, TCategory, TModel>> SearchProduct(string keywords, CultureInfo culture,
            ContentState contentState, ActiveStatus activeStatus);
        IList<ProductBrandViewModel<TBrand, TModel, TCategory>> SearchProductGroupByBrand(string keyword,
            CultureInfo currentCulture);
        IList<TProduct> GetProductsByCategory(Guid categoryId);
        IList<TCategory> GetCategories(Guid productId);
        ProductDto<TBrand, TCategory, TModel> ProductToDto(TProduct product);
    }


    public interface IProductService : IProductService<Brand, Category, Model, BaseProduct>
    {}

    public class ProductService<TProduct, TBrand, TModel, TModelGroup, TCategory, TProductsInCategories> : 
        IProductService<TBrand, TCategory, TModel,TProduct>
        where TProduct : Data.Interfaces.IProduct, new()
        where TBrand : Data.Interfaces.IBrand
        where TModel : Data.Interfaces.IModel
        where TModelGroup : Data.Interfaces.IModelGroup
        where TCategory : Data.Interfaces.ICategory
        where TProductsInCategories : Data.Interfaces.IProductsInCategories
    {
        protected readonly IProductRepository<TProduct> ProductRepository;
        protected readonly IBrandRepository<TBrand> BrandRepository;
        protected readonly IModelRepository<TModel> ModelRepository;
        protected readonly IModelGroupRepository<TModelGroup> ModelGroupRepository;
        protected readonly ICategoryRepository<TCategory> CategoryRepository;
        protected readonly IProductsInCategoriesRepository<TProductsInCategories> ProductsInCategoriesRepository;
        protected readonly ICacheService<ProductBrandViewModel<TBrand, TModel, TCategory>> ProductBrandCacheService;
        protected readonly ICacheService<ProductDto<TBrand, TCategory, TModel>> ProductCacheService;

        public ProductService(IProductRepository<TProduct> productRepository,
            IBrandRepository<TBrand> brandRepository,
            ICategoryRepository<TCategory> categoryRepository, IModelGroupRepository<TModelGroup> modelGroupRepository,
            IModelRepository<TModel> modelRepository,
            IProductsInCategoriesRepository<TProductsInCategories> productsInCategoriesRepository)
        {
            CategoryRepository = categoryRepository;
            ProductRepository = productRepository;
            BrandRepository = brandRepository;
            ModelGroupRepository = modelGroupRepository;
            ModelRepository = modelRepository;
            ProductsInCategoriesRepository = productsInCategoriesRepository;
            ProductBrandCacheService = new CacheService<ProductBrandViewModel<TBrand, TModel, TCategory>>();
            ProductCacheService = new CacheService<ProductDto<TBrand, TCategory, TModel>>();
        }

        public virtual IList<ProductBrandViewModel<TBrand, TModel, TCategory>> GetProductsGroupByBrand(Guid nodeId)
        {
            return GetProductsGroupByBrandForCache(nodeId);
        }

        public virtual IList<ProductBrandViewModel<TBrand, TModel, TCategory>> GetProductGroupByBrand(Guid nodeId)
        {
            return GetProductsGroupByBrandForCache(nodeId);
        }

        public virtual IList<ProductBrandViewModel<TBrand, TModel, TCategory>> SearchProductGroupByBrand(string keywords,
            CultureInfo culture)
        {
            var productBrandsViewModel = new List<ProductBrandViewModel<TBrand, TModel, TCategory>>();
            var productBrand = BrandRepository.All();

            productBrand.ForEach(pm => productBrandsViewModel.Add(CreateProductBrandViewModel(pm)));

            var products = SearchProduct(keywords, culture).OrderBy(product => product.Brand.Name);

            foreach (var product in products)
            {
                foreach (var pb in productBrandsViewModel)
                {
                    if (pb.NodeId == product.Brand.NodeId)
                    {
                        pb.Products.Add(product);
                        break;
                    }
                }
            }

            return productBrandsViewModel.Where(pm => pm.Products.Count > 0).ToList();
        }

        public virtual IList<TProduct> GetProductsByCategory(Guid categoryId)
        {
            return ProductRepository.GetProductsByCategory(categoryId);
        }

        public IList<TCategory> GetCategories(Guid productId)
        {
            //TODO Mettre dans requête subsonic ou storedproc
            var productsInCategoriesList = ProductsInCategoriesRepository.All(CultureInfo.InvariantCulture, ContentState.Draft,
                                                       ActiveStatus.Activated).Where(pic => pic.Products == productId);

            var productGuidList = new List<Guid>();


            productsInCategoriesList.ForEach(pic => productGuidList.Add(pic.Categories.Value));


            return CategoryRepository.LoadByIDs(productGuidList).ToList();
        }

        public virtual ProductDto<TBrand, TCategory, TModel> GetProduct(Guid nodeId)
        {
            //return ProductCacheService.Get(nodeId + "_" + CultureInfo.CurrentCulture,
            //                                BaseProduct.GetCacheDependency(),
            //                                () => GetProductForCache(nodeId));
            return GetProductForCache(nodeId);
        }

        protected virtual ProductDto<TBrand, TCategory, TModel> GetProductForCache(Guid nodeId)
        {
            return ProductToDto(ProductRepository.Get(nodeId));
        }

        public virtual ProductDto<TBrand, TCategory, TModel> GetProduct(Guid productId, CultureInfo culture)
        {
            return GetAllProduct(culture).Single(product => product.NodeId == productId);
        }


        public virtual IList<ProductDto<TBrand, TCategory, TModel>> SearchProduct(string keywords,
            CultureInfo culture, ContentState contentState, ActiveStatus activeStatus)
        {
            IEnumerable<ProductDto<TBrand, TCategory, TModel>> selectedProducts = GetAllProduct(culture, contentState, activeStatus);

            selectedProducts = selectedProducts.Where(product => (product.Brand.Name.ContainsCaseInsensitive(keywords) ||
                  product.Name.ContainsCaseInsensitive(keywords) ||
                  product.Title.ContainsCaseInsensitive(keywords) ||
                  product.ModelGroups.Where(modelGroup =>
                        modelGroup.Models.Where(model => model.Name.ContainsCaseInsensitive(keywords)).Count() > 0).Count() > 0)
                    );

            return selectedProducts.ToList();
        }

        public virtual IList<ProductDto<TBrand, TCategory, TModel>> SearchProduct(string keywords, CultureInfo culture)
        {
            return SearchProduct(keywords, culture, ContentState.Published, ActiveStatus.Activated);
        }

        public virtual IList<ProductDto<TBrand, TCategory, TModel>> GetAllProduct(CultureInfo culture)
        {
            return GetAllProduct(culture, ContentState.Published, ActiveStatus.Activated);
        }

        public virtual IList<ProductDto<TBrand, TCategory, TModel>> GetAllProduct(CultureInfo culture,
            ContentState contentState, ActiveStatus activeStatus)
        {
            return GetAllProductForCache(culture, ContentState.Published, ActiveStatus.Activated);
        }

        protected virtual IList<ProductDto<TBrand, TCategory, TModel>> GetAllProductForCache(CultureInfo culture,
          ContentState contentState, ActiveStatus activeStatus)
        {
            var products = ProductRepository.All(culture, contentState, activeStatus);
            var models = ModelRepository.All(culture, contentState, activeStatus);
            var modelGroups = ModelGroupRepository.All(culture, contentState, activeStatus);
            var brands = BrandRepository.All(culture, contentState, activeStatus);
            var categories = CategoryRepository.All(culture, contentState, activeStatus);
            var productsInCategories = ProductsInCategoriesRepository.All(new CultureInfo(""), contentState, activeStatus);
            var productWithModels = new List<ProductDto<TBrand, TCategory, TModel>>();

            foreach (var product in products)
            {
                productWithModels.Add(
                    new ProductDto<TBrand, TCategory, TModel>
                        {
                            Brand = brands.First(brand => product.Brand == brand.NodeId),
                            ModelGroups = GetModelGroupDto(product.NodeId, modelGroups, models),
                            Title = product.Title,
                            Name = product.Name,
                            Url = product.DefaultUrl.ToString(),
                            SmallImage = product.SmallImage,
                            NodeId = product.NodeId,
                            Categories = GetCategories(product.NodeId, productsInCategories, categories),
                            Height = product.Height.GetValueOrDefault(0),
                            Length = product.Length.GetValueOrDefault(0),
                            Weight = product.Weight.GetValueOrDefault(0),
                            Width = product.Width.GetValueOrDefault(0),
                            Image = product.Image,
                            Models = ModelRepository.GetModelsByParent(product.NodeId, culture.Name,contentState,activeStatus)
                    });
            }

            return productWithModels;
        }

        protected virtual ProductDto<TBrand, TCategory, TModel> CreateProductDTO(TProduct product, CultureInfo culture,
          ContentState contentState, ActiveStatus activeStatus)
        {
            return new ProductDto<TBrand, TCategory, TModel>
               {
                   Title = product.Title,
                   Name = product.Name,
                   NodeId = product.NodeId,
                   SmallImage = product.SmallImage,
                   Url = product.DefaultUrl.ToString(),
                   Brand = BrandRepository.Get(product.Brand.Value),
                   Height = product.Height.GetValueOrDefault(0),
                   Width = product.Width.GetValueOrDefault(0),
                   Weight = product.Weight.GetValueOrDefault(0),
                   Image = product.Image,
                   Length = product.Length.GetValueOrDefault(0),
                   Categories = GetCategories(product.NodeId),
                   ModelGroups = GetModelGroupDto(product.NodeId),
                   Models = ModelRepository.GetModelsByParent(product.NodeId, culture.Name, contentState, activeStatus)
               };
        }


        protected virtual IList<ModelGroupDto<TModel>> GetModelGroupDto(Guid productId)
        {
            var modelGroups = ModelGroupRepository.GetModelGroups(productId);
            var models = new List<TModel>();
 
            modelGroups.ForEach(g => models.AddRange(ModelRepository.GetModels(g.NodeId)));

            return GetModelGroupDto(productId, modelGroups, models);
        }


        protected virtual IList<ModelGroupDto<TModel>> GetModelGroupDto(Guid productId,
            IEnumerable<TModelGroup> modelGroups, IEnumerable<TModel> models)
        {
            return modelGroups
                    .Where(modelGroup => modelGroup.ParentNodeId == productId)
                    .Select(item => new ModelGroupDto<TModel>
                    {
                        NodeId = item.NodeId,
                        Name = item.Name,
                        Models = models.Where(model => model.ParentNodeId == item.NodeId).ToList()
                    })
                    .ToList();
        }

        protected virtual IList<TCategory> GetCategories(Guid productId,
            IEnumerable<TProductsInCategories> productsInCategories, IEnumerable<TCategory> categories)
        {
            var categoryList = new List<TCategory>();

            foreach (var item in productsInCategories.Where(p => p.Products == productId))
            {
                categoryList.Add(categories.Single(categorie => categorie.NodeId == item.Categories));
            }

            return categoryList;
        }

        protected virtual IList<ProductBrandViewModel<TBrand, TModel, TCategory>> GetProductsGroupByBrandForCache(Guid nodeId)
        {
            var productBrandViewModel = new List<ProductBrandViewModel<TBrand, TModel, TCategory>>();
            var brands = BrandRepository.All();
            brands.ForEach(pm => productBrandViewModel.Add(CreateProductBrandViewModel(pm)));

            var products = GetProductsByCategory(nodeId);

            foreach (var product in products)
            {
                foreach (var productBrand in productBrandViewModel)
                {
                    if (productBrand.NodeId == product.Brand.GetValueOrDefault())
                    {
                        productBrand.Products.Add(ProductToDto(product));
                        break;
                    }
                }
            }

            return productBrandViewModel.Where(pm => pm.Products.Count > 0).ToList();
        }

        protected virtual ProductBrandViewModel<TBrand, TModel, TCategory> CreateProductBrandViewModel(TBrand brand)
        {
            return new ProductBrandViewModel<TBrand, TModel, TCategory>
            {
                Name = brand.Name,
                Image = brand.Image,
                NodeId = brand.NodeId,
                Products = new List<ProductDto<TBrand, TCategory, TModel>>()
            };
        }

        public virtual ProductDto<TBrand, TCategory, TModel> ProductToDto(TProduct product)
        {
            return product == null
                       ? new ProductDto<TBrand, TCategory, TModel>()
                       : CreateProductDTO(product,
                                          CultureInfo.GetCultureInfo(product.CultureName), ContentState.Draft,
                                          ActiveStatus.Activated);
        }
    }

    public class ProductService : ProductService<BaseProduct, Brand, Model, ModelGroup, Category, ProductsInCategories>
    {
        public ProductService(IProductRepository<BaseProduct> productRepository,
            IBrandRepository<Brand> brandRepository, 
            ICategoryRepository<Category> categoryRepository, 
            IModelGroupRepository<ModelGroup> modelGroupRepository, 
            IModelRepository<Model> modelRepository, 
            IProductsInCategoriesRepository<ProductsInCategories> productsInCategoriesRepository) 
            : base(productRepository, brandRepository, categoryRepository, modelGroupRepository, modelRepository, productsInCategoriesRepository)
        {
        }

        public override IList<ProductBrandViewModel<Brand, Model, Category>> GetProductsGroupByBrand(Guid nodeId)
        {
            return ProductBrandCacheService.GetCollection("ProductsByBrand_" + nodeId + "_" + CultureInfo.CurrentCulture,
                          BaseProduct.GetCacheDependency(),
                          () => GetProductsGroupByBrandForCache(nodeId));
        }

        public override IList<ProductBrandViewModel<Brand, Model, Category>> GetProductGroupByBrand(Guid nodeId)
        {
            return ProductBrandCacheService.GetCollection("ProductByBrand_" + nodeId + "_" + CultureInfo.CurrentCulture,
                          BaseProduct.GetCacheDependency(),
                          () => GetProductsGroupByBrandForCache(nodeId));
        }

        public override IList<ProductDto<Brand, Category, Model>> GetAllProduct(CultureInfo culture,
            ContentState contentState, ActiveStatus activeStatus)
        {
            var completeCacheDependency = BaseProduct.GetCacheDependency() as AggregateCacheDependency;

            completeCacheDependency.Add(Model.GetCacheDependency(), Brand.GetCacheDependency(),
                ModelGroup.GetCacheDependency(), Model.GetCacheDependency(), Brand.GetCacheDependency(),
                Category.GetCacheDependency(), ProductsInCategories.GetCacheDependency());

            return new CacheService<ProductDto<Brand, Category, Model>>()
                            .GetCollection("ProductDto_" + culture.Name,
                            completeCacheDependency,
                           () => GetAllProductForCache(culture, ContentState.Published, ActiveStatus.Activated));
        }
    }
}
